package sagroup.codegen;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.Properties;

/**
 * Auto generate code of *.java from *Def.properties. The command line
 * parameters are like:<br>
 * .\src\lime2 Command<br>
 * The 1st parameter inform the path where *Def.properties and *.java locate.<br>
 * The 2nd parameter mean the generated java class name.<br>
 * So, above command line parameters input generate Command.java according to
 * CommandDef.properties. Refer to *Def.txt for the format of the definition
 * files.
 * 
 * @author Wenhui Pan
 * 
 */
// TODO refractory with GenWrapper.java, maybe use design patterns such as
// template method etc.
public class GenTuplable {
	private static FieldProperty[] fields;

	private static String path;
	private static String className;

	public static void main(String[] args) throws IOException {
		String[] paras = args;
		while (paras.length != 2) {
			System.out
					.println("Please input parameters such as:.\\src\\lime2\\ Command");
			BufferedReader br = new BufferedReader(new InputStreamReader(
					System.in));
			paras = br.readLine().split(" ");
		}
		path = paras[0];
		className = paras[1];

		creatFields(path, className);
		genCode(path, className);
	}

	private static void genCode(String path, String className) {
		BufferedWriter bw = null;
		try {
			bw = new BufferedWriter(new FileWriter(path + className + ".java"));
			bw.append(genComment());
			bw.append("class " + className + " implements Tuplable " + "{\n");
			bw.append(genDefAndAccess());
			bw.append(genConstructor());
			bw.append(genToString());
			bw.append(genImplTuplable());
			bw.append("}\n");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (bw != null) {
				try {
					bw.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	private static void creatFields(String path, String className) {
		// TODO need close properties file and how to?
		Properties properties = new Properties();
		try {
			properties.load(new FileInputStream(path + "\\" + className
					+ "Def.properties"));
			Enumeration en = properties.propertyNames();
			fields = new FieldProperty[properties.size()];
			int index = 0;

			while (en.hasMoreElements()) {
				String key = (String) en.nextElement();
				String Property = properties.getProperty(key);
				fields[index++] = new FieldProperty(key, Property);
				System.out.println(key + " = " + Property);
			}

			// because we should ensure the order of parametere in all generated
			// methods so we need below
			FieldProperty[] disorderedFields = fields;
			fields = new FieldProperty[properties.size()];
			for (int i = 0; i < fields.length; i++) {
				fields[disorderedFields[i].getPosition().intValue()] = disorderedFields[i];
			}

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private static StringBuffer genComment() {
		StringBuffer sb = new StringBuffer();
		sb.append("package lime2;\n\n\n");
		sb.append("import lights.interfaces.ITuple;\n");
		sb.append("import lights2005util.Tuplable;\n");
		sb.append("import lights2005util.ObjectTuple;\n");
		sb.append("import lights.Field;\n");
		sb.append("/**\n");
		sb
				.append("* immutable class. but because <code>setFromTuple</code> method, so can not\n");
		sb.append("* define fields as final.\n");
		sb.append("*/\n");
		return sb;
	}

	private static StringBuffer genToString() {
		// TODO: use StringBuffer in generated java.
		StringBuffer sb = new StringBuffer();
		sb.append("public String toString() {\n");
		sb.append("return \"[" + className + "]\" +");
		for (int i = 0; i < fields.length; i++) {
			sb.append(fields[i].genToString());
			if (i < fields.length - 1) {
				sb.append(" +");
			}
		}
		sb.append(";\n");
		sb.append("}\n");
		return sb;
	}

	private static StringBuffer genDefAndAccess() {
		StringBuffer sb = new StringBuffer();
		int i = 0;
		for (i = 0; i < fields.length; i++) {
			sb.append(fields[i].genDefAndAccesser());
		}
		return sb;

	}

	private static StringBuffer genImplTuplable() {

		StringBuffer sb = new StringBuffer();

		sb.append("public " + className + "() {}\n");

		sb.append("public ITuple toTuple() {\n");
		sb.append("ObjectTuple t = new ObjectTuple(");
		sb.append(className);
		sb.append(".class);\n");
		for (int i = 0; i < fields.length; i++) {
			sb.append("t.add(new Field().setValue(");
			sb.append(fields[i].getVarName());
			sb.append("));\n");
		}
		sb.append("return t;\n");
		sb.append("}\n");

		sb.append("public void setFromTuple(ITuple tuple) {\n");
		for (int i = 0; i < fields.length; i++) {
			sb.append(fields[i].getVarName());
			sb.append(" = ((");
			sb.append(fields[i].getClassName());
			sb.append(") ((Field) tuple.get(");
			sb.append(i);
			sb.append(")).getValue());\n");
		}
		sb.append("}\n");

		return sb;
	}

	// constructor header
	private static StringBuffer genConstructor() {
		StringBuffer sb = new StringBuffer();
		sb.append("public " + className + "(\n");
		for (int j = 0; j < fields.length; j++) {
			sb.append(fields[j].genConstructorPara());
			if (j != fields.length - 1) {
				sb.append(", ");
			} else {
//				sb.append(", ");
			}
		}
//		sb.append(" ITuple tuple");
		sb.append("){\n");
		// constructor body
		for (int j = 0; j < fields.length; j++) {
			sb.append(fields[j].genConstructorBody());
			if (j != fields.length - 1) {
			} else {
			}
		}
//		sb.append("this.tuple = tuple;\n");
		sb.append("}\n");

		return sb;
	}
}

/**
 * This class maybe reused in many contexts. As to what kind of contexts, use
 * your imagination. For now, it is just used by this file.
 * 
 * @author Wenhui Pan
 * 
 */
// TODO better Methods's modifier?
class FieldProperty {
	private String varName;
	private String className;
	private boolean isPrimitive;
	private boolean canBeNull;
	private Integer position;

	public String getVarName() {
		return varName;
	}

	public String getClassName() {
		return className;
	}

	public boolean isPrimitive() {
		return isPrimitive;
	}

	public boolean isCanBeNull() {
		return canBeNull;
	}

	public Integer getPosition() {
		return position;
	}

	FieldProperty(String varName, String varProperty) {
		String[] ps = varProperty.split(" ");
		this.varName = varName;
		int i = 0;
		this.className = ps[i++];
		this.isPrimitive = Boolean.parseBoolean(ps[i++]);
		this.canBeNull = Boolean.parseBoolean(ps[i++]);
		this.position = Integer.valueOf(ps[i++]);
	}

	public StringBuffer genDefAndAccesser() {
		StringBuffer sb = new StringBuffer();
		sb.append("private ");
		sb.append(className);
		sb.append(" ");
		sb.append(varName);
		sb.append(";\n");

		// getter
		sb.append("public ");
		sb.append(className);
		sb.append(" get");
		String capitalizedFirst = new String(varName.substring(0, 1))
				.toUpperCase();
		sb.append(capitalizedFirst + varName.substring(1));
		sb.append("(){\n");
		sb.append("return ");
		sb.append(varName);
		sb.append(";\n}\n");
		return sb;
	}

	StringBuffer genConstructorPara() {
		return new StringBuffer().append(className + " " + varName);
	}

	StringBuffer genConstructorBody() {
		StringBuffer sb = new StringBuffer();
		sb.append("this." + varName + "=" + varName + ";\n");
		return sb;
	}

	public StringBuffer genToString() {
		StringBuffer sb = new StringBuffer();
		sb.append("\" ");
		sb.append(varName);

		// TODO: commented code is to be removed.
		// if (!isPrimitive) {
		// sb.append("=\" + (");
		// sb.append(varName);
		// sb.append(" == null ? \"null\" : ");
		// sb.append(varName);
		// sb.append(".toString())");
		// } else {
		sb.append("=\" + ");
		sb.append(varName);
		// }
		return sb;
	}

}
